﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Kokomo.Environment;
using Kokomo.Environment.Commanding;
using Kokomo.Environment.Documents;
using Kokomo.Environment.Services;
using Kokomo.Mirror;
using Kokomo.Mirror.CliMetadata;
using Kokomo.Mirror.Ia32;
using Kokomo.Mirror.PeFormat;
using Kokomo.Mirror.PeFormat.Resources;
using Kokomo.Mirror.PeFormat.Symbols;
using Kokomo.PeInspector.Reflection;

namespace Kokomo.PeInspector
{
	class PeModuleViewModel : TreeDocumentViewModel
	{
		public PeModule Module { get; private set; }

		public PeModuleViewModel(
			PeModule module
			)
			: base()
		{
			if (module == null) throw new ArgumentNullException("module");
			this.Module = module;
			this._disassemblyContext = new PeModuleDisassemblyContext(module);

			this.BeginLoadSymbols();

			this.SaveResourceCommand = new ParameterizedCommand(this.SaveResource);

			IImageProvider imageProvider = AssemblyAnalyzerEditor.MetadataIconProvider;

			IList<TreeNodeViewModel> headerNodes = new ObservableCollection<TreeNodeViewModel>();

			headerNodes.Add(this.CreateNodeForHeaderStruct(module.DosHeader));
			headerNodes.Add(this.CreateNodeForHeaderStruct((module.NewExeFileHeader)));
			headerNodes.Add(this.CreateNodeForHeaderStruct((PeHeaderStruct)module.OptionalHeader));

			//// Data directories
			//List<TreeNodeViewModel> dataDirectoryNodes = new List<TreeNodeViewModel>();
			//DataDirectory[] dataDirectories = null;
			//if (module.OptionalHeader32 != null) dataDirectories = module.OptionalHeader32.DataDirectory;
			//else if (module.OptionalHeader64 != null) dataDirectories = module.OptionalHeader64.DataDirectory;
			//if (dataDirectories != null)
			//{
			//    foreach (var dataDirectory in dataDirectories)
			//    {
			//        PeHeaderViewModel viewModel = new PeHeaderViewModel(dataDirectory);
			//        TreeNodeViewModel node = new TreeNodeViewModel(null, dataDirectory., viewModel);
			//        sectionHeaderNodes.Add(node);
			//    }
			//}

			// Section headers
			List<TreeNodeViewModel> sectionHeaderNodes = new List<TreeNodeViewModel>();
			foreach (var sectionHeader in module.SectionHeaders)
			{
				TreeNodeViewModel node = new TreeNodeViewModel(
					imageProvider.GetImage(ImageKeys.Section),
					UiString.FromString(sectionHeader.Name),
					sectionHeader
					);
				sectionHeaderNodes.Add(node);
			}
			headerNodes.Add(new TreeNodeViewModel(
				imageProvider.GetImage(ImageKeys.Sections),
				UiString.FromString(Messages.SectionHeadersTitle),
				new TypedBindingList<SectionHeader>(module.SectionHeaders, UiString.FromString(Messages.SectionsTitle)))
				{
					ItemsSource = sectionHeaderNodes
				});

			List<TreeNodeViewModel> directoryNodes = new List<TreeNodeViewModel>();
			if (module.HasExportDirectory)
			{
				TreeNodeViewModel exportDirectoryNode = new TreeNodeViewModel(
					AssemblyAnalyzerEditor.MetadataIconProvider.GetImage(MetadataIconKeys.Method),
					UiString.FromString(Messages.ExportsTitle),
					new TypedBindingList<ExportedFunction>(module.ExportDirectory.Exports, UiString.FromString(Messages.ExportsTitle)))
					{
						ItemsSourceDelegate = this.BuildExportDirectoryNodes
					};
				directoryNodes.Add(exportDirectoryNode);
			}

			if (module.ResourceRootDirectory != null)
			{
				TreeNodeViewModel resourcesNode = new TreeNodeViewModel(
					null,
					UiString.FromString(Messages.ResourcesTitle),
					new TypedBindingList<ResourceDataDescriptor>(module.ResourceRootDirectory.Items.OfType<ResourceDataDescriptor>().ToArray(), UiString.FromString(Messages.ResourcesTitle)))
					{
						ItemsSourceDelegate = progress => this.CreateResourceNodes(progress, module.ResourceRootDirectory.Items)
					};
				directoryNodes.Add(resourcesNode);
			}

			if (module.HasImportDirectory)
			{
				TreeNodeViewModel importDirectoryNode = BuildImportDirectoryNode(module);
				directoryNodes.Add(importDirectoryNode);
			}
			TreeNodeViewModel dataDirectoriesNode = new TreeNodeViewModel(null, UiString.FromString(Messages.DataDirectoriesTitle), new TypedBindingList<DataDirectoryEntry>(module.OptionalHeader.DataDirectory, UiString.FromString(Messages.DataDirectoriesTitle))) { ItemsSource = directoryNodes };
			headerNodes.Add(dataDirectoriesNode);

			if (module.Cor20Header != null)
			{
				List<object> cor20Items = new List<object>();
				if (module.IsCliAssembly)
				{
					cor20Items.Add(this.CreateNodeForHeaderStruct(module.Cor20MetadataRootHeader));
					cor20Items.Add(this.CreateNodeForHeaderStruct(module.Cor20MetadataStreamHeader));

					List<TreeNodeViewModel> streamHeaderNodes = new List<TreeNodeViewModel>();
					foreach (var streamHeader in module.Cor20MetadataRootHeader.StreamHeaders)
					{
						TreeNodeViewModel node = new TreeNodeViewModel(null, UiString.FromString(streamHeader.Name), streamHeader);
						streamHeaderNodes.Add(node);
					}
					cor20Items.Add(new TreeNodeViewModel(null, UiString.FromString(Messages.MetadataStreamsTitle)) { ItemsSource = streamHeaderNodes });

					//List<MetadataTableViewModel> tableViewModels = new List<MetadataTableViewModel>();
					//foreach (var table in module.MetadataContainer.Tables)
					//{
					//    if (table.RowCount > 0)
					//    {
					//        MetadataTableViewModel tableViewModel = new MetadataTableViewModel(this, table);
					//        tableViewModels.Add(tableViewModel);
					//    }
					//}

					ImageSource tableIcon = imageProvider.GetImage(ImageKeys.Table);

					List<TreeNodeViewModel> tableNameNodes = new List<TreeNodeViewModel>();
					foreach (var table in module.MetadataContainer.Tables.OrderBy(vm => vm.Number.ToString()))
					{
						if (table.RowCount > 0)
						{
							TreeNodeViewModel tableNameNode = new TreeNodeViewModel(
								tableIcon,
								UiString.FromString(string.Format("{0} : 0x{1:X2}", table.Number, (int)table.Number)),
								table
								);
							tableNameNodes.Add(tableNameNode);
						}
					}
					cor20Items.Add(new TreeNodeViewModel(
						tableIcon,
						UiString.FromString(Messages.MetadataTablesByNameTitle)
						) { ItemsSource = tableNameNodes });

					List<TreeNodeViewModel> tableNumberNodes = new List<TreeNodeViewModel>();
					foreach (var table in module.MetadataContainer.Tables.OrderBy(vm => vm.Number))
					{
						if (table.RowCount > 0)
						{
							TreeNodeViewModel tableNumberNode = new TreeNodeViewModel(
								tableIcon,
								UiString.FromString(string.Format("0x{0:X2} : {1}", (int)table.Number, table.Number.ToString())),
								table
								);
							tableNumberNodes.Add(tableNumberNode);
						}
					}

					cor20Items.Add(new TreeNodeViewModel(
						tableIcon,
						UiString.FromString(Messages.MetadataTablesByNumberTitle)
						) { ItemsSource = tableNumberNodes });
				}

				headerNodes.Add(this.CreateNodeForHeaderStruct(module.Cor20Header, cor20Items));
			}

			if (
				(module.OptionalHeader.AddressOfEntryPoint != 0)
				&& (module.IsValidRva(module.OptionalHeader.AddressOfEntryPoint))
				)
			{
				NativeProcedureInfo entryProcedure = module.GetProcedureAtRva(module.OptionalHeader.AddressOfEntryPoint);
				headerNodes.Add(new TreeNodeViewModel(
					AssemblyAnalyzerEditor.MetadataIconProvider.GetImage(MetadataIconKeys.Method),
					UiString.FromString(Messages.EntryPointTitle),
					entryProcedure
					));
			}

			{
				DetailViewModel stringsViewModel = new DetailViewModel(
					this.NavigationContext,
					new DelayLoadViewModel(this.CreateStringsViewModel),
					UiString.FromString(Messages.StringsTitle)
					);
				TreeNodeViewModel stringsNode = new TreeNodeViewModel(
					imageProvider.GetImage(ImageKeys.String),
					UiString.FromString(Messages.StringsTitle),
					stringsViewModel
					);
				headerNodes.Add(stringsNode);
			}

			TreeNodeViewModel symbolsNode = new TreeNodeViewModel(
				imageProvider.GetImage(ImageKeys.Symbols),
				UiString.FromString(Messages.SymbolsTitle),
				null
				)
				{
					ItemsSourceDelegate = GetSymbols
				};
			this.SymbolsNode = symbolsNode;
			headerNodes.Add(symbolsNode);

			ImageSource moduleIconSource = AssemblyAnalyzerEditor.GetIconForModule(module.FileName);
			TreeNodeViewModel topModuleNode = new TreeNodeViewModel(
				moduleIconSource,
				UiString.FromString(Path.GetFileName(module.FileName)),
				module
				)
			{
				ItemsSource = headerNodes,
				IsExpanded = true
			};
			this.HeaderNodes.Add(topModuleNode);

			this.NavigateTo(module);

			//IEnumerable<Ia32Procedure> procedures = module.DiscoverProcedures();
		}

		private SymbolSource _moduleSymbols;
		private bool _attemptedSymbolLoad;
		private Exception _symbolLoadException;
		private PeModuleDisassemblyContext _disassemblyContext;

		public TreeNodeViewModel SymbolsNode { get; private set; }

		private void BeginLoadSymbols()
		{
			new Thread(this.LoadSymbolsAsync) { Name = "PeModuleViewModel.LoadSymbols" }.Start();
		}

		private void LoadSymbolsAsync()
		{
			try
			{
				this._attemptedSymbolLoad = true;
				this._moduleSymbols = SymbolProvider.Default.LoadSymbolsForModule(this.Module.FileName);
				this._disassemblyContext = new PeModuleDisassemblyContext(this.Module, this._moduleSymbols);
			}
			catch (Exception ex)
			{
				this._symbolLoadException = ex;
			}
		}

		private void DisplaySymbolLoadError(Exception ex)
		{
			ErrorDocumentViewModel errorDoc = new ErrorDocumentViewModel()
			{
				Exception = ex,
				MainCaption = UiString.FromString(Messages.ErrorLoadingSymbolsTitle),
				Detail = UiString.FromString(Messages.ErrorLoadingSymbolsDetail)
			};

			this.SymbolsNode.Target = errorDoc;
			this.NavigateTo(errorDoc);
		}

		private IEnumerable<TreeNodeViewModel> GetSymbols(ProgressInfo progress)
		{
			PeModule module = this.Module;

			//try
			//{
			//    module.EnsureSymbols();
			//}
			//catch (Exception ex)
			//{
			//    Application.Current.Dispatcher.BeginInvoke(
			//        (Action<Exception>)this.DisplaySymbolLoadError,
			//        ex
			//        );
			//    return null;
			//}

			if (this._moduleSymbols == null) return null;

			// Build symbol node
			var publicSymbols = this._moduleSymbols.GlobalScope.PublicSymbols.OrderBy(n => new string(n.PrincipalName.Where(char.IsLetterOrDigit).ToArray()));
			List<TreeNodeViewModel> symbolNodes = new List<TreeNodeViewModel>();

			IImageProvider imageProvider = AssemblyAnalyzerEditor.MetadataIconProvider;
			ImageSource symbolImage = imageProvider.GetImage(ImageKeys.Symbol);
			foreach (var publicSymbol in publicSymbols)
			{
				if (publicSymbol.IsFunction)
				{
					try
					{
						uint rva = publicSymbol.RelativeVirtualAddress;
						if (!module.IsValidRva(rva)) rva = publicSymbol.AddressOffset;
						if (module.IsValidRva(rva))
						{
							NativeProcedureInfo procedureInfo = module.GetProcedureAtRva(rva);
							TreeNodeViewModel symbolNode = new TreeNodeViewModel(
								symbolImage,
								UiString.FromString(publicSymbol.PrincipalName),
								procedureInfo
								);
							symbolNodes.Add(symbolNode);
						}
					}
					catch { }
				}
			}

			ImageSource symbolsImage = imageProvider.GetImage(ImageKeys.Symbols);
			var groupNodes = symbolNodes
				.GroupBy(n => char.ToUpper(n.Heading.Value.FirstOrDefault(char.IsLetter)))
				.OrderBy(g => g.Key)
				.Select(g => new TreeNodeViewModel(symbolsImage, (g.Key == '\0') ? UiString.FromString(Messages.OtherTitle) : UiString.FromString(g.Key.ToString())) { ItemsSource = g })
				.ToArray()
				;

			return groupNodes;
		}

		private static TreeNodeViewModel BuildImportDirectoryNode(PeModule module)
		{
			List<TreeNodeViewModel> importedModuleNodes = new List<TreeNodeViewModel>();
			foreach (var importedModule in module.ImportDirectory.ImportedModules)
			{
				string fullImportPath = module.ResolveModule(importedModule.ModuleName);
				ImageSource moduleIcon = null;
				if (
					(fullImportPath != null)
					&& File.Exists(fullImportPath)
					)
				{
					moduleIcon = AssemblyAnalyzerEditor.GetIconForModule(fullImportPath);
				}
				TreeNodeViewModel moduleNode = new TreeNodeViewModel(moduleIcon, UiString.FromString(importedModule.ModuleName), importedModule);
				importedModuleNodes.Add(moduleNode);
			}
			TreeNodeViewModel importDirectoryNode = new TreeNodeViewModel(
				null,
				UiString.FromString(Messages.ImportedModulesTitle),
				new TypedBindingList<ImportedModule>(module.ImportDirectory.ImportedModules, UiString.FromString(Messages.ImportedModulesTitle))
				)
				{
					ItemsSource = importedModuleNodes
				};
			return importDirectoryNode;
		}

		private IEnumerable<TreeNodeViewModel> BuildExportDirectoryNodes(ProgressInfo progress)
		{
			List<TreeNodeViewModel> procedureNodes = new List<TreeNodeViewModel>();

			PeModule module = this.Module;
			ImageSource exportImage = AssemblyAnalyzerEditor.MetadataIconProvider.GetImage(MetadataIconKeys.Method);
			foreach (var export in module.ExportDirectory.Exports)
			{
				if (!export.IsForwarded && module.IsValidRva(export.Rva))
				{
					NativeProcedureInfo procedureInfo = module.GetProcedureAtRva(export.Rva);
					string name = export.DecoratedName;
					if (name == null) name = "#" + export.Ordinal;
					TreeNodeViewModel node = new TreeNodeViewModel(
						exportImage,
						UiString.FromString(name),
						procedureInfo
						);
					procedureNodes.Add(node);
				}
			}

			if (procedureNodes.Count < 50)
			{
				return procedureNodes;
			}
			else
			{
				var groupNodes = procedureNodes
					.GroupBy(n => char.ToUpper(n.Heading.Value.FirstOrDefault(char.IsLetter)))
					.OrderBy(g => g.Key)
					.Select(g => new TreeNodeViewModel(exportImage, (g.Key == '\0') ? UiString.FromString(Messages.OtherTitle) : UiString.FromString(g.Key.ToString())) { ItemsSource = g })
					.ToArray()
					;
				return groupNodes;
			}
		}

		private IEnumerable<TreeNodeViewModel> CreateResourceNodes(
			ProgressInfo progress,
			IList<ResourceItem> resources
			)
		{
			List<TreeNodeViewModel> nodes = new List<TreeNodeViewModel>(resources.Count);

			foreach (var resource in resources)
			{
				string heading;
				if (
					(resource.Path != null)
					&& (resource.Path.Parts.Length > 0)
					)
				{
					if (
						(resource.Path.Parts.Length == 1)
						&& (resource.PredefinedType != ResourceType.Unknown)
						)
					{
						heading = string.Format("{0} [{1}]", resource.PredefinedType, resource.Path.Parts[0].ToString());
					}
					else if (
						(resource.Path.Parts.Length == 3)
						&& (resource.Culture != null)
						)
					{
						heading = string.Format("{0} [{1}]", resource.Culture.DisplayName, resource.Path.Parts[2].ToString());
					}
					else
					{
						ResourcePathPart lastPart = resource.Path.Parts.LastOrDefault();
						heading = lastPart.ToString();
					}
				}
				else
				{
					heading = Messages.UnnamedResourceHeading;
				}

				object target = null;
				ResourceDirectory directory = resource as ResourceDirectory;
				Func<ProgressInfo, IEnumerable<TreeNodeViewModel>> itemsSourceDelegate = null;
				if (directory != null)
				{
					target = new TypedBindingList<ResourceDataDescriptor>(
						directory.Items.OfType<ResourceDataDescriptor>().ToArray(),
						UiString.FromString(directory.Path.ToString())
						);
					itemsSourceDelegate = p => this.CreateResourceNodes(progress, directory.Items);
				}
				else
				{
					target = resource;
				}

				TreeNodeViewModel resourcesNode = new TreeNodeViewModel(
					null,
					UiString.FromString(heading),
					target
					)
					{
						ItemsSourceDelegate = itemsSourceDelegate
					};
				nodes.Add(resourcesNode);
			}

			return nodes;
		}
		private string GetHeading(PeHeaderStruct headerStruct)
		{
			if (headerStruct == null) throw new ArgumentNullException("headerStruct");

			DescriptionAttribute descriptionAttribute = headerStruct.GetType().GetCustomAttribute<DescriptionAttribute>();
			string heading =
				(descriptionAttribute != null)
				? descriptionAttribute.Description
				: headerStruct.GetType().FullName
				;

			return heading;
		}

		private TreeNodeViewModel CreateNodeForHeaderStruct(
			PeHeaderStruct headerStruct,
			IEnumerable itemsSource = null
			)
		{
			ImageSource iconSource = AssemblyAnalyzerEditor.MetadataIconProvider.GetImage(MetadataIconKeys.Property);
			string heading = this.GetHeading(headerStruct);
			TreeNodeViewModel node = new TreeNodeViewModel(iconSource, UiString.FromString(heading), headerStruct);
			node.ItemsSource = itemsSource;

			return node;
		}

		protected override DetailViewModel GetDetailViewModelFor(object obj, NavigationContext navigationContext)
		{
			PeModule module = obj as PeModule;
			if (module != null)
			{
				TabbedDetailViewModel moduleDetail = new TabbedDetailViewModel(
					navigationContext,
					module,
					UiString.FromString(Path.GetFileName(module.FileName))
					);

				moduleDetail.Summary.Details.Add(new SummaryDetailLinkItem(
					UiString.FromString(Messages.FileNameLabel),
					UiString.FromString(module.FileName),
					CommonCommands.BrowseToFileCommand,
					module.FileName
					));

				FileVersionInfo versionInfo = FileVersionInfo.GetVersionInfo(this.Module.FileName);
				moduleDetail.Tabs.Add(new TabPageViewModel(
					UiString.FromString(Messages.VersionHeading),
					new PeHeaderStructViewModel(navigationContext, UiString.FromString(Messages.VersionHeading), versionInfo)
					));

				return moduleDetail;
			}
			else
			{
				PeHeaderStruct headerStruct = obj as PeHeaderStruct;
				if (headerStruct != null)
				{
					string heading = this.GetHeading(headerStruct);
					PeHeaderStructViewModel viewModel = new PeHeaderStructViewModel(navigationContext, UiString.FromString(heading), headerStruct);
					return viewModel;
				}
				else
				{
					IBindingList bindingList = obj as IBindingList;
					if (bindingList != null)
					{
						string tableName = null;
						ITypedList typedList = bindingList as ITypedList;
						if (typedList != null) tableName = typedList.GetListName(null);
						TableViewModel table = new TableViewModel(
							navigationContext,
							UiString.FromString(tableName),
							bindingList
							);
						return table;
					}
					else
					{
						ResourceDataDescriptor resource = obj as ResourceDataDescriptor;
						if (resource != null)
						{
							return this.GetDetailViewModelForResource(resource, navigationContext);
						}
						else
						{
							MetadataTable metadataTable = obj as MetadataTable;
							if (metadataTable != null)
							{
								MetadataTableViewModel tableViewModel = new MetadataTableViewModel(navigationContext, metadataTable);
								return tableViewModel;
							}
							else
							{
								NativeProcedureInfo procedure = obj as NativeProcedureInfo;
								if (procedure != null)
								{
									return this.GetDetailViewModelForProcedure(procedure, navigationContext);
								}
								else
								{
									PeModuleRef? moduleRef = obj as PeModuleRef?;
									if (moduleRef.HasValue)
									{
										EnvironmentServices.DocumentManager.OpenDocumentFromFile(moduleRef.Value.fileName);
										return null;
									}
									else
									{
										ImportedModule importedModule = obj as ImportedModule;
										if (importedModule != null)
										{
											return this.GetDetailViewModelForImportedModule(importedModule, navigationContext);
										}
										else
										{
											SymbolicReference symbolicRef = obj as SymbolicReference;
											if (symbolicRef != null)
											{
												if (symbolicRef.ModuleName == null)
												{
													uint procedureRva = this.Module.ResolveExportName(symbolicRef.SymbolName);
													NativeProcedureInfo targetProcedure = this.Module.GetProcedureAtRva(procedureRva);
													if (targetProcedure != null)
													{
														navigationContext.NavigateTo(targetProcedure);
													}
												}
												else
												{
													string modulePath = this.Module.ResolveModule(symbolicRef.ModuleName);
													if (modulePath != null)
													{
														PeModuleViewModel viewModel = EnvironmentServices.DocumentManager.OpenDocumentFromFile(modulePath) as PeModuleViewModel;
														if (viewModel != null)
														{
															viewModel.IsFloating = this.IsFloating;
															viewModel.NavigateTo(new SymbolicReference(symbolicRef.SymbolName, symbolicRef.Offset));
														}
													}
												}

												return null;
											}
											else
											{
												return base.GetDetailViewModelFor(obj, navigationContext);
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}

		private DetailViewModel GetDetailViewModelForResource(ResourceDataDescriptor resource, NavigationContext navigationContext)
		{
			TabbedDetailViewModel detail = new TabbedDetailViewModel(
				navigationContext,
				resource,
				UiString.FromString(resource.Path.Parts.Last().ToString())
				);

			ResourceType predefinedType = resource.PredefinedType;
			if (predefinedType != null)
				detail.Summary.Details.Add(new SummaryDetailItem(UiString.FromString(Messages.PredefinedTypeLabel), UiString.FromString(predefinedType.ToString())));

			if (resource.Culture != null)
				detail.Summary.Details.Add(new SummaryDetailItem(UiString.FromString(Messages.CultureLabel), UiString.FromString(resource.Culture.DisplayName)));

			detail.Summary.Commands.Add(new CommandViewModel(this.SaveResourceCommand, UiString.FromString(Messages.SaveResourceToFileText)) { Argument = resource });

			object preview = null;
			switch (predefinedType)
			{
				//case ResourceType.Icon:
				case ResourceType.Bitmap:
					{
						byte[] data = resource.GetData();

						MemoryStream stream = new MemoryStream(data);
						ImageSource imageSource = BitmapFrame.Create(stream);
						Image image = new Image() { Source = imageSource };
						preview = image;
					}
					break;
				case ResourceType.Dialog:
					{
						DialogTemplate dialog = resource.LoadObject() as DialogTemplate;
						if (dialog != null)
						{
							preview = new DialogTemplateViewModel(dialog);
						}
					}
					break;
			}

			if (preview != null)
			{
				detail.Tabs.Add(new TabPageViewModel(UiString.FromString(Messages.PreviewTitle), preview));
			}

			return detail;
		}

		public ICommand SaveResourceCommand { get; private set; }
		private void SaveResource(object arg)
		{
			ResourceDataDescriptor resource = arg as ResourceDataDescriptor;
			if (resource == null) return;

			// Determine file type
			FileType fileType = null;
			switch (resource.PredefinedType)
			{
				case ResourceType.Cursor:
					fileType = ResourceFileTypes.Cursor;
					break;
				case ResourceType.Bitmap:
					fileType = ResourceFileTypes.Bitmap;
					break;
				case ResourceType.Icon:
					fileType = ResourceFileTypes.Icon;
					break;
				case ResourceType.Font:
					fileType = ResourceFileTypes.Font;
					break;
				case ResourceType.AnimatedCursor:
					fileType = ResourceFileTypes.AnimatedCursor;
					break;
				case ResourceType.Html:
					fileType = ResourceFileTypes.Html;
					break;
				default:
					fileType = ResourceFileTypes.Data;
					break;
			}

			IUiService ui = EnvironmentServices.DemandService<IUiService>();
			string fileName = ui.ShowSaveFileDialog(new FileDialogOptions()
			{
				FileTypes = new FileType[] { fileType },
				Title = UiString.FromString(Messages.SaveResourceTitle)
			});

			if (fileName != null)
			{
				byte[] resourceData = resource.GetData();
				using (FileStream stream = new FileStream(fileName, FileMode.Create))
				{
					stream.Write(resourceData, 0, resourceData.Length);
				}
			}
		}

		private DetailViewModel GetDetailViewModelForProcedure(NativeProcedureInfo procedure, NavigationContext navigationContext)
		{
			if (procedure == null) throw new ArgumentNullException("procedure");

			string name = procedure.DecoratedName;
			string heading = procedure.PrincipalName;
			if (heading == null)
			{
				heading = string.Format("? 0x{0:X8}", procedure.Rva);
			}
			TabbedDetailViewModel detail = new TabbedDetailViewModel(navigationContext, procedure, UiString.FromString(heading));

			if (name != null)
			{
				detail.Summary.Details.Add(new SummaryDetailItem(UiString.FromString(Messages.DecoratedNameLabel), UiString.FromString(name)));
				detail.Summary.Details.Add(new SummaryDetailItem(UiString.FromString(Messages.UndecoratedNameLabel), UiString.FromString(DebugHelper.UndecorateSymbolName(name))));
			}

			detail.Summary.Details.Add(new SummaryDetailItem(UiString.FromString(Messages.BaseAddressLabel), UiString.FromString("0x" + procedure.BaseAddress.ToString("X8"))));

			SourceDocumentViewModel assemblySourceViewModel = new SourceDocumentViewModel(
				(writer) => DisassemblyDocumentBuilder.BuildDisassemblyDocument(procedure, writer),
				navigationContext,
				AssemblyAnalyzerEditor.AssemblyListingFileType,
				procedure.DecoratedName
				);
			detail.Tabs.Add(new TabPageViewModel(UiString.FromString(Messages.DisassemblyTitle), assemblySourceViewModel));
			// Source
			SourceDocumentViewModel sourceViewModel = new SourceDocumentViewModel(
				(writer) => DisassemblyDocumentBuilder.BuildSourceDocument(procedure, writer),
				navigationContext,
				AssemblyAnalyzerEditor.AssemblyListingFileType,
				procedure.DecoratedName
				);
			detail.Tabs.Add(new TabPageViewModel(UiString.FromString(Messages.SourceTitle), sourceViewModel));

			return detail;
		}

		private DetailViewModel GetDetailViewModelForImportedModule(ImportedModule importedModule, NavigationContext navigationContext)
		{
			TableViewModel detail = new TableViewModel(navigationContext, UiString.FromString(importedModule.ModuleName), new TypedBindingList<ImportedFunction>(importedModule.Functions, UiString.FromString(importedModule.ModuleName)));
			string modulePath = this.Module.ResolveModule(importedModule.ModuleName);
			if (modulePath != null)
			{
				detail.Summary.Details.Add(new SummaryDetailLinkItem(
					UiString.FromString(Messages.PathLabel),
					UiString.FromString(modulePath),
					navigationContext.NavigateToCommand,
					new PeModuleRef() { fileName = modulePath }
					));
			}

			return detail;
		}

		private static bool IsDataSection(SectionHeader section)
		{
			bool isDataSection =
				section.Characteristics.HasFlag(SectionCharacteristics.InitializedData & SectionCharacteristics.Readable)
				&& !section.Characteristics.HasFlag(SectionCharacteristics.Executable)
				;

			return isDataSection;
		}

		private object CreateStringsViewModel(ProgressInfo progress)
		{
			IEnumerable<SectionHeader> sections = this.Module.SectionHeaders.Where(IsDataSection).ToArray();

			int totalBytes = 0;
			foreach (var section in sections)
				totalBytes += (int)section.SizeOfRawData;

			int totalWork = totalBytes / 1024;

			progress.Maximum = totalWork;

			List<PeModuleString> moduleStrings = new List<PeModuleString>();

			long moduleBaseAddress = this.Module.OptionalHeader.ImageBase;
			int progressBytes = 0;
			foreach (var section in sections)
			{
				progress.Text = UiString.FromString(string.Format(Messages.ScanningSectionText, section.Name));

				byte[] sectionData = this.Module.ReadSectionData(section.Name);
				Stream stream = new MemoryStream(sectionData);

				IEnumerable<ScannedString> scannedStrings = StringScanner.Scan(
					stream,
					bytesRead =>
					{
						progressBytes += 2;
						progress.Progress = progressBytes / 1024;
					}
				);

				foreach (var scannedString in scannedStrings)
				{
					uint rva = section.VirtualAddress + (uint)scannedString.StartOffset;
					PeModuleString moduleString = new PeModuleString(
						scannedString.Text,
						rva + moduleBaseAddress,
						section.Name,
						rva
						);
					moduleStrings.Add(moduleString);
				}
			}

			PeModuleStringsViewModel viewModel = new PeModuleStringsViewModel(
				this.Module,
				moduleStrings
				);
			return viewModel;
		}
	}
}
